﻿using System;
using System.Linq;
using System.Reflection;
using System.Transactions;
using Codeflows.Results;
using Codeflows.Tracking;

namespace Codeflows.Dispatch
{
    class SyncActionMethod : ActionMethodDescriptor
    {
        readonly TransactionalAttribute _transactionalAttribute;

        public SyncActionMethod(MethodInfo methodInfo, Func<Codeflow, ActionContext, Result> callsite)
            : base(methodInfo)
        {
            if (callsite == null) throw new ArgumentNullException("callsite");
            Callsite = callsite;

            _transactionalAttribute =
                (TransactionalAttribute)
                methodInfo.GetCustomAttributes(typeof (TransactionalAttribute), false).FirstOrDefault();
        }

        public Func<Codeflow, ActionContext, Result> Callsite { get; private set; }

        public override Result ExecuteAction(Codeflow instance, CodeflowInstance codeflowInstance)
        {
            return _transactionalAttribute == null
                       ? ExecuteWithoutTransaction(instance, codeflowInstance)
                       : ExecuteWithTransaction(instance, codeflowInstance);
        }

        Result ExecuteWithTransaction(Codeflow instance, CodeflowInstance codeflowInstance)
        {
            var hasAnAmibientTransaction = Transaction.Current != null;
            using (var scope = new TransactionScope(TransactionScopeOption.Required, _transactionalAttribute.Options))
            {
                var result = Execute(instance, codeflowInstance);

                if (DateTime.UtcNow > codeflowInstance.LockedUntilUtc)
                    throw new TimeoutException("Timeout allocated for the current execution cycle has expired.");

                if (!hasAnAmibientTransaction)
                    scope.Complete();

                return result;
            }
        }

        Result ExecuteWithoutTransaction(Codeflow instance, CodeflowInstance codeflowInstance)
        {
            var result = Execute(instance, codeflowInstance);

            if (DateTime.UtcNow > codeflowInstance.LockedUntilUtc)
                throw new TimeoutException("Timeout allocated for the current execution cycle has expired.");

            return result;
        }

        Result Execute(Codeflow instance, CodeflowInstance codeflowInstance)
        {
            try
            {
                var context = new ActionContext(codeflowInstance);
                var result = Callsite(instance, context);
                return result;
            }
            catch (Exception e)
            {
                if (DiagnosticsUtility.IsFatal(e))
                    throw;

                return new SuspendedResult(e);
            }
        }
    }
}